package jobs

import (
	"bytes"
	"cronjob/models"
	"errors"
	"fmt"
	"net"
	"os/exec"
	"runtime"
	"time"

	"golang.org/x/crypto/ssh"

	"github.com/astaxie/beego"
)

func InitJob() {
	//遍历所有已经启动的任务
	list, _ := models.TaskGetList(1, 1000, "status", 1)

	///fmt.Println("Job初始化了")
	//fmt.Println("---------------------------------")
	//fmt.Println(len(list))
	//fmt.Println("---------------------------------")
	//遍历任务列表
	for _, task := range list {
		job, err := NewJobFromTask(task)
		if err != nil {
			beego.Error("InitJobs:", err.Error())
			continue
		}
		//beego.Info(task.Id)
		AddJob(task.CronSpec, job)
	}

}

// 根据task创建job
func NewJobFromTask(task *models.Task) (*Job, error) {
	if task.Id < 0 {
		return nil, fmt.Errorf("缺少id!")
	}

	//本地执行
	if task.ServerId == 0 {
		beego.Info("本地执行", task.Id)

		job := NewCommandJob(task.Id, task.TaskName, task.Command)
		job.task = task
		job.Concurrent = task.Concurrent == 1
		return job, nil
	}

	//服务器执行,根据服务器id查询服务器信息
	server, _ := models.TaskSeverGetById(task.ServerId)

	if server.Type == 0 { //密码登录
		//远程服务器执行
		beego.Info("远程服务器执行", task.Id)
		job := RomoteCommandJobByPassword(task.Id, task.TaskName, task.Command, server)
		job.task = task
		job.Concurrent = task.Concurrent == 1
		return job, nil
	}

	//

	return nil, errors.New("Job创建失败!")
}

//NewCommandJob
func NewCommandJob(id int, name string, command string) *Job {
	job := &Job{
		id:   id,
		name: name,
	}

	beego.Info(command)

	//执行命令
	//job.returnFun = func(duration time.Duration) (string, string, error, bool) {}
	job.returnFun = func(timeout time.Duration) (string, string, error, bool) {

		var cmd *exec.Cmd
		if runtime.GOOS == "windows" {
			cmd = exec.Command("CMD", "/C", command)
		} else {
			cmd = exec.Command("sh", "-c", command)
		}

		buffOut := new(bytes.Buffer)
		buffErr := new(bytes.Buffer)

		cmd.Stdout = buffOut ////重定向输出
		cmd.Stderr = buffErr //重定向错误
		//执行命令
		cmd.Start()

		//等待命令结束释放资源
		err, isTimeout := runCmdWithTimeOut(cmd, timeout)
		return buffOut.String(), buffErr.String(), err, isTimeout
	}

	return job
}

//error：错误信息
//bool:是否超时
func runCmdWithTimeOut(cmd *exec.Cmd, timeout time.Duration) (error, bool) {
	done := make(chan error)

	go func() {
		//等待命令结束，释放资源
		done <- cmd.Wait()
	}()

	var err error

	select {
	//超时处理
	case <-time.After(timeout):
		beego.Warn(fmt.Sprintf("任务执行超时，进程将被强制杀死:%d", cmd.Process.Pid))

		go func() {
			<-done
		}()

		if err = cmd.Process.Kill(); err != nil {
			beego.Error(fmt.Sprintf("进程无法杀掉：%d, 错误信息: %s", cmd.Process.Pid, err))
		}
		return err, true
	//程序执行完成
	case err = <-done:
		return err, false
	}
}

//远程登录
func RomoteCommandJobByPassword(id int, name string, command string, server *models.TaskServer) *Job {

	var (
		auth         []ssh.AuthMethod
		clientConfig *ssh.ClientConfig
		client       *ssh.Client
		err          error
		session      *ssh.Session
	)

	job := &Job{
		id:   id,
		name: name,
	}

	//执行命令
	job.returnFun = func(timeout time.Duration) (string, string, error, bool) {
		// ssh.AuthMethod里面存放了ssh的认证方式，如果使用密码进行认证，
		//  ssh.Password 来加载密码
		auth = make([]ssh.AuthMethod, 0)
		auth = append(auth, ssh.Password(server.Password))

		//创建客户端配置
		clientConfig = &ssh.ClientConfig{
			User: server.ServerAccount,
			Auth: auth,

			//需要验证服务端，不做验证返回nil就可以，点击HostKeyCallback看源码就知道了
			HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
				return nil
			},
		}

		// 创建连接地址凭接地址
		addr := fmt.Sprintf("%s:%d", server.ServerIp, server.Port)

		//创建client
		if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
			return "", "", err, false
		}

		defer client.Close()

		//创建连接
		if session, err = client.NewSession(); err != nil {
			return "", "", err, false
		}

		bufOut := new(bytes.Buffer)
		bufErr := new(bytes.Buffer)
		session.Stdout = bufOut //重定向输出
		session.Stderr = bufErr //重定向错误

		if err = session.Run(command); err != nil {
			return "", "", err, false
		}
		return bufOut.String(), bufErr.String(), err, false
	}

	return job
}
